package aiaudio.collaborative;

import aiaudio.storage.api.IArtist;
import aiaudio.storage.api.IAudioTrainingSet;
import aiaudio.storage.api.IUser;
import aiaudio.storage.api.InternalException;
import aiaudio.storage.api.Utils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.neuroph.core.exceptions.VectorSizeMismatchException;
import org.neuroph.core.learning.TrainingElement;
import org.neuroph.core.learning.TrainingSet;
import org.neuroph.nnet.Kohonen;

/**
 *
 * @author Nastya
 */
public class CollaborativeAudioTrainingNetwork implements Serializable {

    private Kohonen network;
    private List<IUser> users;
    private List<IArtist> artists;
    private IAudioTrainingSet audioTrainingSet;
    private CollaborativeGroupping groupping;
    private int trainingCiclesCount;
    private static final int GROUP_DIVISOR = 1;
    private static final double NORMALIZATION = 0.3;

    public CollaborativeAudioTrainingNetwork(IAudioTrainingSet audioTrainingSet,
            int trainingCiclesCount) {
        this.audioTrainingSet = audioTrainingSet;
        this.trainingCiclesCount = trainingCiclesCount;
        initializeNetwork(audioTrainingSet);
    }

    private void initializeNetwork(IAudioTrainingSet audioTrainingSet) {
        artists = Utils.convertToList(audioTrainingSet.getArtistsIterator());
        users = Utils.convertToList(audioTrainingSet.getUsersIterator());
        network = new Kohonen(artists.size(), users.size() / GROUP_DIVISOR);
        network.randomizeWeights();
    }

    public void process() throws InternalException {
        startLearning(audioTrainingSet.getTrainingRatings(artists, users));
    }

    private void addToAllGroups(double[] output, IUser user) {
        for (int i = 0; i < output.length; i++) {
            groupping.add(user, i, 1);
        }
    }

    private void addToPertinentGroups(double[] outputs, Double min, Double max,
            IUser user) {
        for (int i = 0; i < outputs.length; i++) {
            double output = outputs[i];
            double normalized = (output - min) / (max - min);
            if (normalized < NORMALIZATION) {
                groupping.add(user, i, 1 - normalized);
            }
        }
    }

    private void addUserToGroups(IUser user) throws VectorSizeMismatchException,
            InternalException {
        double[] output = calculateOnNetwork(user);
        List<Double> outputAsList = toList(output);
        Double max = Collections.max(outputAsList);
        Double min = Collections.min(outputAsList);

        if (max == min) {
            addToAllGroups(output, user);
        } else {
            addToPertinentGroups(output, min, max, user);
        }
    }

    private List<Double> toList(double[] output) {
        List<Double> outputAsList = new ArrayList<Double>();
        for (int i = 0; i < output.length; i++) {
            outputAsList.add(output[i]);
        }
        return outputAsList;
    }

    private double[] calculateOnNetwork(IUser user)
            throws VectorSizeMismatchException, InternalException {
        double[] input = audioTrainingSet.getTrainingRatings(artists, user);
        network.setInput(input);
        network.calculate();
        double[] output = network.getOutput();
        return output;
    }

    private void startLearning(Iterator<double[]> learningData) {
        TrainingSet trainingSet = collectTrainingSet(learningData);
        for (int i = 0; i < trainingCiclesCount; i++) {
            network.learnInSameThread(trainingSet);
        }
    }

    private TrainingSet collectTrainingSet(Iterator<double[]> learningData) {
        TrainingSet trainingSet = new TrainingSet();
        while (learningData.hasNext()) {
            double[] ratings = learningData.next();
            trainingSet.addElement(new TrainingElement(ratings));
        }
        return trainingSet;
    }

    public void group() throws InternalException {
        groupping = new CollaborativeGroupping();

        for (IUser user : users) {
            addUserToGroups(user);
        }
    }

    public CollaborativeGroupping getGroupping() {
        return groupping;
    }
}
